package com.authsphere.security.configurer.verifycode.provider;

import com.authsphere.common.verifiy.VerificationCodeAuthenticator;
import com.authsphere.security.authenticator.api.Authenticator;
import com.authsphere.security.common.user.EnhanceUserDetailsService;
import com.authsphere.security.configurer.verifycode.SmsVerificationCodeAuthenticationToken;
import com.authsphere.security.configurer.verifycode.exception.VerifyCodeException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;

/**
 * @program: AuthSphere
 * @description:
 * @author: YuKai Fan
 * @create: 2025/3/11 16:39
 **/
public class SmsVerificationCodeAuthenticationProvider extends VerificationCodeAuthenticationProvider {
    private Authenticator authenticator;

    private EnhanceUserDetailsService userDetailsService;

    /**
     * Creates a new instance using the provided {@link VerificationCodeAuthenticator}
     *
     * @param authenticator the {@link VerificationCodeAuthenticator} to use. Cannot be null.
     * @since 6.0.3
     */
    public SmsVerificationCodeAuthenticationProvider(Authenticator authenticator) {
        setSmsAuthenticator(authenticator);
    }

    @Override
    protected void additionalAuthenticationChecks(SmsVerificationCodeAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Failed to authenticate since no credentials provided");
            throw new VerifyCodeException(this.messages
                    .getMessage("VerificationCodeAuthenticationProvider.verifyCodeException", "Bad verifyCode"));
        }
        String presentedPassword = authentication.getCredentials().toString();
        if (!this.authenticator.matches(authentication.getPrincipal().toString(), presentedPassword)) {
            logger.debug("Failed to authenticate since verifyCode does not match stored value");
            throw new VerifyCodeException(this.messages
                    .getMessage("VerificationCodeAuthenticationProvider.verifyCodeException", "Bad verifyCode"));
        }
    }

    @Override
    protected UserDetails retrieveUser(String username, SmsVerificationCodeAuthenticationToken authentication) throws AuthenticationException {
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        } catch (UsernameNotFoundException | InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
                                                         UserDetails user) {
        return super.createSuccessAuthentication(principal, authentication, user);
    }

    /**
     * Sets the VerificationCodeAuthenticator instance to be used to encode and validate verificationCode. If
     * not set, the verificationCode will be compared using
     *
     * @param authenticator must be an instance of one of the {@code VerificationCodeAuthenticator}
     *                        types.
     */

    public void setSmsAuthenticator(Authenticator authenticator) {
        Assert.notNull(authenticator, "authenticator cannot be null");
        this.authenticator = authenticator;
    }

    public EnhanceUserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(EnhanceUserDetailsService userDetailsService) {
        Assert.notNull(authenticator, "userDetailsService cannot be null");
        this.userDetailsService = userDetailsService;
    }
}
